package com.exception.global.core.service.impl;

import com.exception.global.core.domain.SubjectSpecification;
import com.exception.global.core.util.CollectionUtil;
import com.exception.global.core.repository.BaseRepository;
import com.exception.global.core.service.BaseService;
import com.exception.global.core.vo.PageVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;

import javax.persistence.criteria.Predicate;
import javax.transaction.Transactional;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
import java.util.Collection;
import java.util.List;
import java.util.Optional;

/**
 * 基础service实现
 * 将事务细化到方法，不在类中直接使用事务，仅针对 save/delete 相关修改数据的方法添加事务，保证数据的 ACID
 */
public class BaseServiceImpl<T, ID extends Serializable, Repository extends BaseRepository<T, ID>> implements BaseService<T, ID> {

    @Autowired
    private Repository repository;

    @Override
    public PageVO<T> findAll(@NotNull Integer pageSize, @NotNull Integer pageNum, String[] orderFields,
                             Sort.Direction direction, String[] fieldNames, Object[] values) {
        // 处理空指针问题
        if (orderFields == null || orderFields.length < 1) {
            orderFields = new String[1];
            // 确保业务表中一定存在的字段，如果有统一定义 BaseEntity,也可改成对应的字段，如：createTime
            orderFields[0] = "id";
        }
        if (direction == null) {
            direction = Sort.Direction.ASC;
        }
        // 分页查询
        Pageable pageable = PageRequest.of(pageNum, pageSize, direction, orderFields);
        // 可直接提供字段名方式或者根据VO解析
        Page<T> page = repository.findAll((root, query, cb) -> {
            Predicate predicate = cb.conjunction();
            for (int i = 0; i < fieldNames.length; i++) {
                predicate.getExpressions().add(cb.equal(root.get(fieldNames[i]), values[i]));
            }
            return predicate;
        }, pageable);
        // 需要统一定义返回分页查询的vo
        PageVO<T> retPage = new PageVO<>(page.getNumber(), page.getSize());
        Long total = page.getTotalElements();
        List<T> data = page.getContent();
        retPage.setRows(data);
        retPage.setTotal(total);
        return retPage;
    }

    @Override
    public PageVO<T> findAll(@NotNull Specification<T> specification, Pageable pageable) {
        // 分页查询
        Page<T> page = repository.findAll(specification, pageable);
        // 解析查询结果，按要求格式返回
        PageVO<T> retPage = new PageVO<>(page.getNumber(), page.getSize());
        Long total = page.getTotalElements();
        List<T> data = page.getContent();
        retPage.setRows(data);
        retPage.setTotal(total);
        return retPage;
    }

    @Override
    public T findById(@NotNull ID id) {
        Optional option = repository.findById(id);
        if (option.isPresent()) {
            return (T) option.get();
        }
        return null;
    }

    @Override
    public T findByUnique(@NotNull String fieldName, @NotNull Object value) {
        Optional option = repository.findOne((root, query, cb) -> cb.equal(root.get(fieldName), value));
        if (option.isPresent()) {
            return (T) option.get();
        }
        return null;
    }

    @Override
    public T findByUnique(String[] fieldNames, Object[] values) {
        Assert.notNull(fieldNames, "fieldNames cannot be null");
        Assert.notNull(values, "values cannot be null");

        Optional option = repository.findOne((root, query, cb) -> {
            Predicate predicate = cb.conjunction();
            for (int i = 0; i < fieldNames.length; i++) {
                predicate.getExpressions().add(cb.equal(root.get(fieldNames[i]), values[i]));
            }
            return predicate;
        });
        if (option.isPresent()) {
            return (T) option.get();
        }
        return null;
    }

    @Override
    public List<T> findBy(String[] fieldNames, Object[] values) {
        Assert.notNull(fieldNames, "fieldNames cannot be null");
        Assert.notNull(values, "values cannot be null");

        return repository.findAll((root, query, cb) -> {
            Predicate predicate = cb.conjunction();
            for (int i = 0; i < fieldNames.length; i++) {
                predicate.getExpressions().add(cb.equal(root.get(fieldNames[i]), values[i]));
            }
            return predicate;
        });
    }

    @Override
    public List<T> findBy(String fieldName, Object value) {
        Assert.notNull(fieldName, "fieldName cannot be null");

        return repository.findAll((root, query, cb) -> cb.equal(root.get(fieldName), value));
    }

    @Override
    public List<T> findBy(String[] fieldNames, Object[] values, String[] orderFields, Sort.Direction direction) {
        Assert.notNull(fieldNames, "fieldNames cannot be null");
        Assert.notNull(values, "values cannot be null");
        Assert.notNull(orderFields, "orderFields cannot be null");
        Assert.notNull(direction, "direction cannot be null");
        assertLength(fieldNames, values);

        Sort sort =  new Sort(direction, orderFields);
        return repository.findAll((root, query, cb) -> {
            Predicate predicate = cb.conjunction();
            for (int i = 0; i < fieldNames.length; i++) {
                predicate.getExpressions().add(cb.equal(root.get(fieldNames[i]), values[i]));
            }
            return predicate;
        }, sort);
    }

    @Override
    public List<T> findBy(String fieldName, Object value, String[] orderFields, Sort.Direction direction) {
        Assert.notNull(fieldName, "fieldName cannot be null");
        Assert.notNull(orderFields, "orderFields cannot be null");
        Assert.notNull(direction, "direction cannot be null");

        Sort sort =  new Sort(direction, orderFields);
        return repository.findAll((root, query, cb) -> cb.equal(root.get(fieldName), value), sort);
    }

    @Override
    public List<T> findBy(SubjectSpecification<T> spec) {
        return repository.findAll(spec);
    }

    @Override
    public List<T> findBy(SubjectSpecification<T> spec, Sort sort) {
        return repository.findAll(spec, sort);
    }

    @Override
    @Transactional
    public T save(T t) {
        Assert.notNull(t, "The saved object cannot be null.");
        return repository.save(t);
    }

    @Override
    @Transactional
    public List<T> save(Collection<T> ts) {
        CollectionUtil.notNull(ts, "The saved collection cannot be null or empty.");
        return repository.saveAll(ts);
    }

    @Override
    @Transactional
    public T deleteById(ID id) {
        Optional optional = repository.findById(id);
        if (optional.isPresent()) {
            T t = (T) optional.get();
            repository.delete(t);
            return t;
        }
        return null;
    }

    @Override
    public void deleteBy(String[] fieldNames, Object[] values) {
        Assert.notNull(fieldNames, "fieldNames cannot be null");
        Assert.notNull(values, "values cannot be null");

        List<T> list = findBy(fieldNames, values);
        if (CollectionUtils.isEmpty(list)) {
            repository.deleteAll(list);
        }
    }

    /**
     * 断言长度不一致
     * @param o1
     * @param o2
     */
    private void assertLength(Object[] o1, Object[] o2) {
        if (o1.length != o2.length) {
            throw new IllegalArgumentException("The length of the elements is inconsistent.");
        }
    }
}
